package com.edmodo.cropper;

import com.edmodo.cropper.customutils.AttrUtils;
import com.edmodo.cropper.customutils.RectF;
import com.edmodo.cropper.edge.Edge;
import com.edmodo.cropper.handle.Handle;
import com.edmodo.cropper.util.*;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

public class CropImageView extends Image implements Component.DrawTask, Component.TouchEventListener {
    public static final int GUIDELINES_ON_TOUCH = 1;
    public static final int GUIDELINES_ON = 2;

    private Paint mBorderPaint;
    private Paint mGuidelinePaint;
    private Paint mCornerPaint;
    private Paint mSurroundingAreaOverlayPaint;

    private float mHandleRadius;
    private float mSnapRadius;
    private float mCornerThickness;
    private float mBorderThickness;
    private float mCornerLength;

    private int mImgWidth;
    private float mImgHeight;
    private float mScale = 0.5f;
    private float mAngle = 0.0f;

    private Context context;
    private Matrix mMatrix = null;

    private RectF mBitmapRect = new RectF();
    private RectF mFrameRect;
    private Point mTouchOffset = new Point();
    private Handle mPressedHandle;

    private boolean mFixAspectRatio;
    private int mAspectRatioX;
    private int mAspectRatioY;
    private int mGuidelinesMode;

    public CropImageView(Context context) {
        super(context);
        this.context = context;
        init(context, null);
    }

    public CropImageView(Context context, AttrSet attrs) {
        super(context, attrs);
        this.context = context;
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        String linesMode = AttrUtils.getStringFromAttr(attrs, "guidelines", "onTouch");
        mGuidelinesMode = guidelines(linesMode);
        mFixAspectRatio = AttrUtils.getBooleanFromAttr(attrs, "fixAspectRatio", false);
        mAspectRatioX = AttrUtils.getIntFromAttr(attrs, "aspectRatioX", 1);
        mAspectRatioY = AttrUtils.getIntFromAttr(attrs, "aspectRatioY", 1);

        mBorderPaint = PaintUtil.newBorderPaint(context);
        mGuidelinePaint = PaintUtil.newGuidelinePaint(context);
        mSurroundingAreaOverlayPaint = PaintUtil.newSurroundingAreaOverlayPaint(context);
        mCornerPaint = PaintUtil.newCornerPaint(context);

        mHandleRadius = AttrHelper.vp2px(24, context);
        mSnapRadius = AttrHelper.vp2px(3, context);
        mBorderThickness = AttrHelper.vp2px(3, context);
        mCornerThickness = AttrHelper.vp2px(5, context);
        mCornerLength = AttrHelper.vp2px(20, context);

        mMatrix = new Matrix();

        setLayoutRefreshedListener(new RefreshListener());
        addDrawTask(this);
        setTouchEventListener(this);

    }

    private float left;
    private float top;
    private float width;
    private float height;

    @Override
    public void onDraw(Component component, Canvas canvas) {
        final int viewWidth = component.getWidth();
        final int viewHeight = component.getHeight();
        mImgWidth = viewWidth - getPaddingLeft() - getPaddingRight();
        mImgHeight = viewHeight - getPaddingTop() - getPaddingBottom();

        drawDarkenedSurroundingArea(canvas);
        drawGuidelines(canvas);
        drawBorder(canvas);
        drawCorners(canvas);
        left = mFrameRect.left;
        top = mFrameRect.top;
        width = mFrameRect.width();
        height = mFrameRect.getHeight();

    }

    @Override
    public void setPixelMap(PixelMap pixelMap) {
        super.setPixelMap(pixelMap);
        invalidate();
    }

    @Override
    public void setPixelMap(int resId) {
        super.setPixelMap(resId);
        invalidate();
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float touchX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                touchX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return touchX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float touchY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                touchY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return touchY;
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!isEnabled()) {
            return false;
        }
        float mLastX = getTouchX(event,0);
        float mLastY = getTouchY(event,0);
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                onActionDown(mLastX, mLastY);
                return true;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                onActionUp();
                return true;
            case TouchEvent.POINT_MOVE:
                onActionMove(mLastX, mLastY);
                return true;
            default:
                return false;
        }
    }

    public void setGuidelines(int guidelinesMode) {
        mGuidelinesMode = guidelinesMode;
        invalidate();
    }

    public void setFixedAspectRatio(boolean fixAspectRatio) {
        mFixAspectRatio = fixAspectRatio;
        postLayout();
    }

    public void setAspectRatio(int aspectRatioX, int aspectRatioY) {
        if (aspectRatioX <= 0 || aspectRatioY <= 0) {
            throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
        }
        mAspectRatioX = aspectRatioX;
        mAspectRatioY = aspectRatioY;

        if (mFixAspectRatio) {
            postLayout();
        }
    }

    public PixelMap getCroppedImage() {
        PixelMap source = getPixelMap();
        if (source == null) {
            return null;
        }
        float left = Edge.LEFT.getCoordinate()*source.getImageInfo().size.width/mImgWidth;
        float top = Edge.TOP.getCoordinate()*source.getImageInfo().size.height/mImgHeight;

        float right = Edge.RIGHT.getCoordinate()*source.getImageInfo().size.width/mImgWidth;
        float bottom = Edge.BOTTOM.getCoordinate()*source.getImageInfo().size.height/mImgHeight;

        float width = right -left;
        float height = bottom -top;

        float w = Math.min(width,source.getImageInfo().size.width-left);
        float h = Math.min(height,source.getImageInfo().size.height-top);

        int minX = Math.round(left );
        int minY = Math.round(top);
        int cropWidth = Math.round(w);
        int cropHeight = Math.round(h);

        Rect cropperRect = new Rect(minX, minY,cropWidth,cropHeight);
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        initializationOptions.size = new Size(cropWidth, cropHeight);
        return PixelMap.create(source, cropperRect, initializationOptions);
    }

    private RectF getBitmapRect() {
        PixelMap source = getPixelMap();
        if (source == null) {
            return null;
        }
        RectF getRect = new RectF();

        getRect.modify(0, 0, mImgWidth, mImgHeight);
        return getRect;
    }

    private void initCropWindow(RectF bitmapRect) {
        if (mFixAspectRatio) {
            initCropWindowWithFixedAspectRatio(bitmapRect);
        } else {
            final float horizontalPadding = 0.1f * bitmapRect.getWidth();
            final float verticalPadding = 0.1f * bitmapRect.getHeight();

            Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding);
            Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding);
            Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding);
            Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding);
        }
    }

    private void initCropWindowWithFixedAspectRatio(RectF bitmapRect) {
        if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > getTargetAspectRatio()) {
            final float cropWidth = AspectRatioUtil.calculateWidth(bitmapRect.getHeight(), getTargetAspectRatio());
            Edge.LEFT.setCoordinate(bitmapRect.getCenter().position[0] - cropWidth / 2f);
            Edge.TOP.setCoordinate(bitmapRect.top);
            Edge.RIGHT.setCoordinate(bitmapRect.getCenter().position[0] + cropWidth / 2f);
            Edge.BOTTOM.setCoordinate(bitmapRect.bottom);

        } else {
            final float cropHeight = AspectRatioUtil.calculateHeight(bitmapRect.getWidth(), getTargetAspectRatio());
            Edge.LEFT.setCoordinate(bitmapRect.left);
            Edge.TOP.setCoordinate(bitmapRect.getCenter().position[1] - cropHeight / 2f);
            Edge.RIGHT.setCoordinate(bitmapRect.right);
            Edge.BOTTOM.setCoordinate(bitmapRect.getCenter().position[1] + cropHeight / 2f);
        }
    }

    private void drawDarkenedSurroundingArea(Canvas canvas) {
        final RectF bitmapRect = mBitmapRect;

        final float left = Edge.LEFT.getCoordinate();
        final float top = Edge.TOP.getCoordinate();
        final float right = Edge.RIGHT.getCoordinate();
        final float bottom = Edge.BOTTOM.getCoordinate();

        RectF rectFTop = new RectF();
        rectFTop.modify(bitmapRect.left, bitmapRect.top, bitmapRect.right, top);
        canvas.drawRect(rectFTop, mSurroundingAreaOverlayPaint);

        RectF rectFBottom = new RectF();
        rectFBottom.modify(bitmapRect.left, bottom, bitmapRect.right, bitmapRect.bottom);
        canvas.drawRect(rectFBottom, mSurroundingAreaOverlayPaint);

        RectF rectFLeft = new RectF();
        rectFLeft.modify(bitmapRect.left, top, left, bottom);
        canvas.drawRect(rectFLeft, mSurroundingAreaOverlayPaint);

        RectF rectFRight = new RectF();
        rectFRight.modify(right, top, bitmapRect.right, bottom);
        canvas.drawRect(rectFRight, mSurroundingAreaOverlayPaint);
    }

    private void drawGuidelines(Canvas canvas) {
        if (!shouldGuidelinesBeShown()) {
            return;
        }

        final float left = Edge.LEFT.getCoordinate();
        final float top = Edge.TOP.getCoordinate();
        final float right = Edge.RIGHT.getCoordinate();
        final float bottom = Edge.BOTTOM.getCoordinate();

        final float oneThirdCropWidth = Edge.getWidth() / 3;

        final float x1 = left + oneThirdCropWidth;
        Point x1StartPoint = new Point(x1, top);
        Point x1EndPoint = new Point(x1, bottom);
        canvas.drawLine(x1StartPoint, x1EndPoint, mGuidelinePaint);

        final float x2 = right - oneThirdCropWidth;
        Point x2StartPoint = new Point(x2, top);
        Point x2EndPoint = new Point(x2, bottom);
        canvas.drawLine(x2StartPoint, x2EndPoint, mGuidelinePaint);

        final float oneThirdCropHeight = Edge.getHeight() / 3;

        final float y1 = top + oneThirdCropHeight;
        Point y1StartPoint = new Point(left, y1);
        Point y1EndPoint = new Point(right, y1);
        canvas.drawLine(y1StartPoint, y1EndPoint, mGuidelinePaint);

        final float y2 = bottom - oneThirdCropHeight;
        Point y2StartPoint = new Point(left, y2);
        Point y2EndPoint = new Point(right, y2);
        canvas.drawLine(y2StartPoint, y2EndPoint, mGuidelinePaint);
    }

    private void drawBorder(Canvas canvas) {
        mFrameRect = new RectF();
        mFrameRect.modify(
                Edge.LEFT.getCoordinate(),
                Edge.TOP.getCoordinate(),
                Edge.RIGHT.getCoordinate(),
                Edge.BOTTOM.getCoordinate());
        canvas.drawRect(mFrameRect, mBorderPaint);
    }

    private void drawCorners(Canvas canvas) {
        final float left = Edge.LEFT.getCoordinate();
        final float top = Edge.TOP.getCoordinate();
        final float right = Edge.RIGHT.getCoordinate();
        final float bottom = Edge.BOTTOM.getCoordinate();

        final float lateralOffset = (mCornerThickness - mBorderThickness) / 2f;

        final float startOffset = mCornerThickness - (mBorderThickness / 2f);

        Point topLeftLleftStart = new Point(left - lateralOffset, top - startOffset);
        Point topLeftLeftEnd = new Point(left - lateralOffset, top + mCornerLength);
        canvas.drawLine(topLeftLleftStart, topLeftLeftEnd, mCornerPaint);

        Point topLeftTopStart = new Point(left - startOffset, top - lateralOffset);
        Point top_left_top_end = new Point(left + mCornerLength, top - lateralOffset);
        canvas.drawLine(topLeftTopStart, top_left_top_end, mCornerPaint);

        Point topRightRightStart = new Point(right + lateralOffset, top - startOffset);
        Point topRightRightEnd = new Point(right + lateralOffset, top + mCornerLength);
        canvas.drawLine(topRightRightStart, topRightRightEnd, mCornerPaint);

        Point topRightTopStart = new Point(right + startOffset, top - lateralOffset);
        Point topRightTopEnd = new Point(right - mCornerLength, top - lateralOffset);
        canvas.drawLine(topRightTopStart, topRightTopEnd, mCornerPaint);

        Point bottomLeftLeftStart = new Point(left - lateralOffset, bottom + startOffset);
        Point bottomLeftLeftEnd = new Point(left - lateralOffset, bottom - mCornerLength);
        canvas.drawLine(bottomLeftLeftStart, bottomLeftLeftEnd, mCornerPaint);

        Point bottomLeftBottomStart = new Point(left - startOffset, bottom + lateralOffset);
        Point bottomLeftBottomEnd = new Point(left + mCornerLength, bottom + lateralOffset);
        canvas.drawLine(bottomLeftBottomStart, bottomLeftBottomEnd, mCornerPaint);

        Point bottomRightRightStart = new Point(right + lateralOffset, bottom + startOffset);
        Point bottomRightRightEnd = new Point(right + lateralOffset, bottom - mCornerLength);
        canvas.drawLine(bottomRightRightStart, bottomRightRightEnd, mCornerPaint);

        Point bottomRightBottomStart = new Point(right + startOffset, bottom + lateralOffset);
        Point bottomRightBottomEnd = new Point(right - mCornerLength, bottom + lateralOffset);
        canvas.drawLine(bottomRightBottomStart, bottomRightBottomEnd, mCornerPaint);
    }

    private boolean shouldGuidelinesBeShown() {
        return ((mGuidelinesMode == GUIDELINES_ON)
                || ((mGuidelinesMode == GUIDELINES_ON_TOUCH) && (mPressedHandle != null)));
    }

    private float getTargetAspectRatio() {
        return mAspectRatioX / (float) mAspectRatioY;
    }

    private void onActionDown(float x, float y) {
        final float left = Edge.LEFT.getCoordinate();
        final float top = Edge.TOP.getCoordinate();
        final float right = Edge.RIGHT.getCoordinate();
        final float bottom = Edge.BOTTOM.getCoordinate();

        mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);

        if (mPressedHandle != null) {
            HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom, mTouchOffset);
            invalidate();
        }
    }

    private void onActionUp() {
        if (mPressedHandle != null) {
            mPressedHandle = null;
            invalidate();
        }
    }

    private void onActionMove(float x, float y) {
        if (mPressedHandle == null) {
            return;
        }

        x += mTouchOffset.position[0];
        y += mTouchOffset.position[1];

        if (mFixAspectRatio) {
            mPressedHandle.updateCropWindow(x, y, getTargetAspectRatio(), mBitmapRect, mSnapRadius);
        } else {
            mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);
        }
        invalidate();
    }

    class RefreshListener implements LayoutRefreshedListener {
        @Override
        public void onRefreshed(Component component) {
            mBitmapRect = getBitmapRect();
            initCropWindow(getBitmapRect());
            invalidate();
        }
    }

    private int guidelines(String str) {
        if ("off".equals(str)) {
            return 0;
        } else if ("onTouch".equals(str)) {
            return 1;
        } else if ("on".equals(str)) {
            return 2;
        } else {
            return 1;
        }
    }
}
